/*
  flamingo - 2D Game Engine
  Copyright (C) 2009 Bradley Zeis

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.

  Bradley Zeis
  flamingoengine@gmail.com
*/

#define FLTIME      1

#include "flamingo.h"
#include <time.h>
#include "fltime.h"

#ifndef CLK_PER_SEC
#define CLK_PER_SEC 1000
#endif

#ifndef CLOCKS_PER_SECOND
#define CLOCKS_PER_SECOND CLK_PER_SEC
#endif

#define SECONDS(t) (t / (double)CLOCKS_PER_SECOND)
#define TICKS(s) ((int)(s * CLOCKS_PER_SECOND))

#define POST_EVENT(timer, type) PyObject *result = PyObject_CallFunction(POST, "Oi", timer, type); Py_DECREF(result);

#define TIMER_START 32
#define TIMER_END 33
#define TIMER_TICK 34

PyTypeObject __declspec(dllexport) Clock_Type;
PyTypeObject __declspec(dllexport) Timer_Type;

Clock *master_clock;
PyObject *ACTIVE_TIMERS;
PyObject *event_module;
PyObject *POST;

/* -------- FLTIME METHODS -------- */
PyObject *ticks(PyObject *self, PyObject *args) {
    return Py_BuildValue("l", (long int)clock());
}

PyObject *sync(PyObject *self, PyObject *args) {
    
    long int c;
    PyArg_ParseTuple(args, "l", &c);
    
    Timer *timer;
    int i, length = PySequence_Length(ACTIVE_TIMERS);
    int offset = 0;
    
    for (i = 0; i < length; i++) {
        timer = (Timer *)PyList_GET_ITEM(ACTIVE_TIMERS, i - offset);
        if (Timer_sync(timer, c) == -2) {
            if (PySequence_DelItem(ACTIVE_TIMERS, i) == -1) {
                return NULL;
            }
            offset += 1;
        }
    } 
    Py_RETURN_NONE;
}

PyObject *set_master_clock(PyObject *self, PyObject *args) {
    master_clock = (Clock *)PySequence_GetItem(args, 0);
    Py_RETURN_NONE;
}

PyObject *ticks_to_seconds(PyObject *self, PyObject *args) {
    int ticks;
    if (!PyArg_ParseTuple(args, "i", &ticks)) {
        PyErr_SetString(PyExc_TypeError, "expected type 'int'");
        return NULL;
    }
    return Py_BuildValue("d", SECONDS(ticks));
}

PyObject *seconds_to_ticks(PyObject *self, PyObject *args) {
    int seconds;
    if (!PyArg_ParseTuple(args, "d", &seconds)) {
        PyErr_SetString(PyExc_TypeError, "expected type 'float'");
        return NULL;
    }
    return Py_BuildValue("i", TICKS(seconds));
}

/* -------- CLOCK METHODS -------- */
/* Initialization */
void Clock_dealloc(Clock *self) {
    self->ob_type->tp_free((PyObject *)self);
}

PyObject *Clock_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
    Clock *self;
    self = (Clock *)type->tp_alloc(type, 0);

    if (self != NULL) {
        self->offset = 0;
        self->current = 0;
        self->last = 0;
        self->fps = 0;
    }

    return (PyObject *)self;
}

int Clock_init(Clock *self, PyObject *args) {
    return 0;
}

PyObject *Clock_user_init(Clock *self, PyObject *args) {
    self->offset = (long int)clock();
    self->current = 0;
    self->last = 0;
    self->fps = 0;
    Py_RETURN_NONE;
}

PyObject *Clock_sync(Clock *self, PyObject *args) {
    if (self->flags & TIME_PAUSED) {
        self->offset = (long int)clock() - self->current;
        Py_RETURN_NONE;
    } else {
        self->last = self->current;
        self->current = (long int)clock() - self->offset;
        if (self->current - self->last > 0)
            self->fps = (self->fps + (CLOCKS_PER_SECOND / (1.0 * self->current - self->last))) / 2.0;
    }
    Py_RETURN_NONE;
}

PyObject *Clock_pause(Clock *self, PyObject *args) {
    self->flags |= TIME_PAUSED;
    Py_RETURN_NONE;
}

PyObject *Clock_resume(Clock *self, PyObject *args) {
    self->flags &= ~TIME_PAUSED;
    Py_RETURN_NONE;
}

PyObject *Clock_ticks(Clock *self, PyObject *args) {
    return Py_BuildValue("i", self->current);
}

PyObject *Clock_fps(Clock *self, PyObject *args) {
    return Py_BuildValue("d", self->fps);
}

/* Presentation */
PyObject *Clock_repr(Clock *self) {
    char string[64];
    sprintf(string, "<fltime.Clock(%i)>", (int)self->current);
    return PyString_FromString(string);
}

PyObject *Clock_str(Clock *self) {
    return Clock_repr(self);
}

/* Method Declaration */
PyMemberDef Clock_members[] = {
    {NULL}
};

PyMethodDef Clock_methods[] = {
    {"init", (PyCFunction)Clock_user_init, METH_NOARGS, NULL},
    {"sync", (PyCFunction)Clock_sync, METH_NOARGS, NULL},
    {"pause", (PyCFunction)Clock_pause, METH_NOARGS, NULL},
    {"resume", (PyCFunction)Clock_resume, METH_NOARGS, NULL},
    {"ticks", (PyCFunction)Clock_ticks, METH_NOARGS, NULL},
    {"fps", (PyCFunction)Clock_fps, METH_NOARGS, NULL},
    {NULL}
};

/* -------- TIMER METHODS -------- */
/* Initialization */
void Timer_dealloc(Timer *self) {
    self->ob_type->tp_free((PyObject *)self);
}

PyObject *Timer_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
    Timer *self;
    self = (Timer *)type->tp_alloc(type, 0);

    if (self != NULL) {
        
    }

    return (PyObject *)self;
}

int Timer_init(Timer *self, PyObject *args) {
    long int length;
    if (!PyArg_ParseTuple(args, "i", &length))
        return -1;
    
    self->current = 0;
    self->start = 0;
    self->length = length;
    return 0;
}

int Timer_sync(Timer *self, long int c) {
    self->current = c - self->start;
    
    if (self->length - self->current > 0) {
        POST_EVENT(self, TIMER_TICK);
        return 0;
    
    } else {
        self->flags &= ~TIMER_ACTIVE;
        POST_EVENT(self, TIMER_END);
        return -2;
    }
}

PyObject *Timer_start(Timer *self, PyObject *args) {
    if (self->flags & TIMER_ACTIVE)
        Py_RETURN_NONE;
        
    if (PyList_Append(ACTIVE_TIMERS, (PyObject *)self) == -1)
        return NULL;
    self->flags |= TIMER_ACTIVE;
    self->start = (long int)clock() - master_clock->offset;
    self->current = 0;
    
    POST_EVENT(self, TIMER_START);
    
    Py_RETURN_NONE;
}

PyObject *Timer_pause(Timer *self, PyObject *args) {
    self->flags |= TIME_PAUSED;
    Py_RETURN_NONE;
}

PyObject *Timer_resume(Timer *self, PyObject *args) {
    self->flags &= ~TIME_PAUSED;
    Py_RETURN_NONE;
}

PyObject *Timer_end(Timer *self, PyObject *args) {
    if (!(self->flags & TIMER_ACTIVE))
        Py_RETURN_NONE;
        
    if (PySequence_DelItem(ACTIVE_TIMERS, PySequence_Index(ACTIVE_TIMERS, (PyObject *)self)) == -1)
        return NULL;
    self->flags &= ~TIMER_ACTIVE;
    
    POST_EVENT(self, TIMER_END);
    
    Py_RETURN_NONE;
}

PyObject *Timer_time(Timer *self, PyObject *args) {
    if (self->flags & TIMER_ACTIVE)
        return Py_BuildValue("i", self->current);
    Py_RETURN_NONE;
}

PyObject *Timer_time_left(Timer *self, PyObject *args) {
    if (self->flags & TIMER_ACTIVE)
        return Py_BuildValue("i", self->length - self->current);
    Py_RETURN_NONE;
}

PyObject *Timer_percent(Timer *self, PyObject *args) {
    if (self->flags & TIMER_ACTIVE)
        return Py_BuildValue("d", 1.0 * self->current/self->length);
    Py_RETURN_NONE;
}

PyObject *Timer_percent_left(Timer *self, PyObject *args) {
    if (self->flags & TIMER_ACTIVE)
        return Py_BuildValue("d", 1.0 * (self->length - self->current)/self->length);
    Py_RETURN_NONE;
}

/* Presentation */
PyObject *Timer_repr(Timer *self) {
    char string[64];
    sprintf(string, "<fltime.Timer(%i)>", (int)self);
    return PyString_FromString(string);
}

PyObject *Timer_str(Timer *self) {
    return Timer_repr(self);
}

/* Method Declaration */
PyMemberDef Timer_members[] = {
    {"length", T_LONG, offsetof(Timer, length), 0, NULL},
    {NULL}
};

PyMethodDef Timer_methods[] = {
    {"start", (PyCFunction)Timer_start, METH_NOARGS, NULL},
    {"pause", (PyCFunction)Timer_pause, METH_NOARGS, NULL},
    {"resume", (PyCFunction)Timer_resume, METH_NOARGS, NULL},
    {"time", (PyCFunction)Timer_time, METH_NOARGS, NULL},
    {"time_left", (PyCFunction)Timer_time_left, METH_NOARGS, NULL},
    {"percent", (PyCFunction)Timer_percent, METH_NOARGS, NULL},
    {"percent_left", (PyCFunction)Timer_percent_left, METH_NOARGS, NULL},
    {NULL}
};

/* -------- TYPE DECLARATIONS -------- */
PyTypeObject Clock_Type = {
    PyObject_HEAD_INIT(NULL)
    0,                                          /* ob_size */
    "flamingo.fltime.Clock",                    /* tp_name */
    sizeof(Clock),                              /* tp_basicsize */
    0,                                          /* tp_itemsize */
    (destructor)Clock_dealloc,                  /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_compare */
    (reprfunc)Clock_repr,                       /* tp_repr */
    0,                                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    (reprfunc)Clock_str,                        /* tp_str */
    0,                                          /*i tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES,   /* tp_flags */
    0,                                          /* tp_doc */
    0,                                      /* tp_traverse */
    0,                                      /* tp_clear */
    0,                                      /* tp_richcompare */
    0,                                      /* tp_weaklistoffset */
    0,                                      /* tp_iter */
    0,                                      /* tp_iternext */
    Clock_methods,                              /* tp_methods */
    0,                                          /* tp_members */
    0,                                          /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    (initproc)Clock_init,                       /* tp_init */
    0,                                          /* tp_alloc */
    Clock_new,                                  /* tp_new */
};

PyTypeObject Timer_Type = {
    PyObject_HEAD_INIT(NULL)
    0,                                          /* ob_size */
    "flamingo.fltime.Timer",                    /* tp_name */
    sizeof(Timer),                              /* tp_basicsize */
    0,                                          /* tp_itemsize */
    (destructor)Timer_dealloc,                  /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_compare */
    (reprfunc)Timer_repr,                       /* tp_repr */
    0,                                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    (reprfunc)Timer_str,                        /* tp_str */
    0,                                          /*i tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES,   /* tp_flags */
    0,                                          /* tp_doc */
    0,                                      /* tp_traverse */
    0,                                      /* tp_clear */
    0,                                      /* tp_richcompare */
    0,                                      /* tp_weaklistoffset */
    0,                                      /* tp_iter */
    0,                                      /* tp_iternext */
    Timer_methods,                              /* tp_methods */
    Timer_members,                              /* tp_members */
    0,                                          /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    (initproc)Timer_init,                       /* tp_init */
    0,                                          /* tp_alloc */
    Timer_new,                                  /* tp_new */
};

/* -------- MODULE INIT -------- */
PyMethodDef fltime_methods[] = {
    {"ticks", (PyCFunction)ticks, METH_NOARGS, NULL},
    {"_timer_sync", (PyCFunction)sync, METH_VARARGS, NULL},
    {"_set_master_clock", (PyCFunction)set_master_clock, METH_VARARGS, NULL},
    {"ticks_to_seconds", (PyCFunction)ticks_to_seconds, METH_VARARGS, NULL},
    {"seconds_to_ticks", (PyCFunction)seconds_to_ticks, METH_VARARGS, NULL},
    {NULL}
};

PyMODINIT_FUNC
initfltime(void)
{
    PyObject *m;
    
    event_module = ImportModule("flamingo.event");
    if (event_module == NULL)
        Py_Exit(0);
        
    POST = PyObject_GetAttrString(event_module, "post");
    if (POST == NULL)
        return;
    
    ACTIVE_TIMERS = PyList_New(0);
    if (ACTIVE_TIMERS == NULL)
        return;

    if (PyType_Ready(&Clock_Type) < 0)
        return;
    if (PyType_Ready(&Timer_Type) < 0)
        return;

    m = Py_InitModule3("fltime", fltime_methods, NULL);
    if (m == NULL)
        return;

    Py_INCREF(&Clock_Type);
    PyModule_AddObject(m, "Clock", (PyObject *)&Clock_Type);
    Py_INCREF(&Timer_Type);
    PyModule_AddObject(m, "Timer", (PyObject *)&Timer_Type);
    
    Py_DECREF(event_module);
}
